/*
* Copyright (c) 2009, Active Web Solutions Limited
* All rights reserved.
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1- Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* 2- Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* 3- The name of the author may not be used to endorse or promote products
* derived from this software without specific prior written permission.
*
*
* THIS SOFTWARE IS PROVIDED BY Active Web Solutions Limited ''AS IS'' AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL Active Web Solutions Limited BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
*/
package net.aws.jazure;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;

import net.aws.jazure.exception.APIRequestFailedException;
import net.aws.jazure.util.ATOMHelper;

import org.restlet.Client;
import org.restlet.data.ChallengeResponse;
import org.restlet.data.ChallengeScheme;
import org.restlet.data.Conditions;
import org.restlet.data.Form;
import org.restlet.data.MediaType;
import org.restlet.data.Method;
import org.restlet.data.Preference;
import org.restlet.data.Protocol;
import org.restlet.data.Request;
import org.restlet.data.Response;
import org.restlet.data.Status;
import org.restlet.data.Tag;

/**
 * <p>The Table class.</p>
 * <p>Used to access entities in the table.</p>
 * 
 * @author dyaa.albakour
 *
 */
public class Table {
	
	private String accountName;
	private String tableName;
	private String accountSharedKey;
	private String endPoint;
	
	/**
	 * A constructor with default access modifier.
	 * for package use only. 
	 * 
	 * @param endPoint
	 * @param tableName
	 * @param accountName
	 * @param accountSharedKey
	 */
	Table(String endPoint, String tableName, String accountName, String accountSharedKey) {
		this.endPoint = endPoint;
		this.accountName = accountName;
		this.tableName = tableName;
		this.accountSharedKey = accountSharedKey;
	}
	
	public String getTableName() {
		return this.tableName;
	}
	
	
	/**
	 * Retrieves a table entity from the table given its partition and row keys.
	 * 
	 * @param partitionKey the partition key of the entity
	 * @param rowKey the row key the row key of the entity
	 * @return a TableStorageEntity object representing the entity.
	 * @throws APIRequestFailedException If the API request return an unexpected HTTP status code.
	 */
	public TableStorageEntity getEntity(String partitionKey, String rowKey) 
		throws APIRequestFailedException{
		
			//TODO retrieve the etag		
			TableStorageEntity entity =null;
			
			Client client = new Client(Protocol.HTTP); 
			
			ChallengeResponse challengeResponse =  new ChallengeResponse(
		    	ChallengeScheme.HTTP_MS_SHAREDKEY_LITE,
		    	accountName,
		    	accountSharedKey);
			String url = endPoint + "/" + this.tableName + "(" + 
						"PartitionKey='"+ partitionKey +"',"+ 
						"RowKey='" +rowKey + "'" +
						")";
			Request request = new Request(Method.GET, url);
			request.setChallengeResponse(challengeResponse);		
			Response response = client.handle(request);		
			
			// Now retrieve the entity if the request succeeds
			if(response.getStatus().isSuccess()){				
				InputStream responseStream;
				try {
					responseStream = response.getEntity().getStream();
					entity = ATOMHelper.getEntityFromATOMFeed(responseStream);
				} catch (IOException e) {
					return null;
				} catch (Exception e) {
					return null;
				}				
				return entity;
				// I couldn't parse the ATOM feed entry with Restlet extension
				//   So I did it with JDOM..
				/*
				Entry feedEntry = new Entry(response.getEntity());
				List<Entry> feedEntries = feed.getEntries();
				// there is only one entry expected
				Entry feedEntry = feedEntries.get(0);
				
				
				DomRepresentation domrepresentation =
					new DomRepresentation(feedEntry.getContent().getInlineContent());				
				Node propertiesNode =  domrepresentation.getNode("/properties");
					
				NodeList nodelist = propertiesNode.getChildNodes();
				for(int i=0;i<nodelist.getLength();i++){
						
					Node node = nodelist.item(i);
					TableStorageProperty property = new TableStorageProperty();
					String type =node.getAttributes().item(0).getTextContent();
						
					property.unmrshallValue(type , node.getTextContent() );
					entity.getProperties().put(node.getNodeName(),property);				
				}*/			
			}
			else
				throw new APIRequestFailedException(response.getStatus());
		
	}
	
	/**
	 * Insert the given table entity in the table storage.
	 * @param entity the entity to insert
	 * @return true if the entity is successfully inserted
	 * @throws APIRequestFailedException If the API request return an unexpected HTTP status code.
	 */
	public boolean insertEntity(TableStorageEntity entity) throws APIRequestFailedException{
		try{
			Client client = new Client(Protocol.HTTP); 
			ChallengeResponse challengeResponse =  new ChallengeResponse(
		    	ChallengeScheme.HTTP_MS_SHAREDKEY_LITE,
		    	accountName,
		    	accountSharedKey);
			
			String url = endPoint + "/" + this.tableName;
			Request request = new Request(Method.POST, url);
			request.setChallengeResponse(challengeResponse);		
			
			String xmlfeed = ATOMHelper.getInsertEntityATOMFeed(entity);				
			
			request.setEntity(xmlfeed,new MediaType("application/atom+xml"));	
			
			Response response = client.handle(request);		
			
	//		System.out.println(response.getStatus());
			
	//		Form form = (Form) response.getAttributes().get("org.restlet.http.headers");
	//		Iterator<Parameter> it= form.iterator();
	//		while(it.hasNext()){
	//			Parameter p =  it.next();
	//			System.out.println(p.getName());
	//			System.out.println(p.getValue());
	//		}				
			Status responseStatus = response.getStatus();
			if( responseStatus.equals(Status.SUCCESS_CREATED) )
				return true;
			else 
				throw new APIRequestFailedException(responseStatus);
		}catch (IOException e) {
			//getInsertEntityATOMFeed failed
			return false;
		}	
	}
	
	
	/**
	 * Update the given entity(identified by the PK,RK pair) in the table.
	 * @param entity the entity to update
	 * @return true if it was updated successfully.
	 * @throws APIRequestFailedException If the API request return an unexpected HTTP status code.
	 */
	public boolean updateEntity(TableStorageEntity entity) throws APIRequestFailedException{
		
		try{
			Client client = new Client(Protocol.HTTP); 
			ChallengeResponse challengeResponse =  new ChallengeResponse(
		    	ChallengeScheme.HTTP_MS_SHAREDKEY_LITE,
		    	accountName,
		    	accountSharedKey);		
			String url = endPoint + "/" + this.tableName + "(" + 
						"PartitionKey='"+ entity.getPartitionKey() +"',"+ 
						"RowKey='" +entity.getRowKey() + "'" +
						")"; 		
			
			Request request = new Request(Method.PUT, url);
			request.setChallengeResponse(challengeResponse);
			
			String xmlfeed= ATOMHelper.getUpdateEntityATOMFeed(entity,url);		
					
			
			request.setEntity(xmlfeed,new MediaType("application/atom+xml"));		
			
			// You can't do that because (if-match is a standard HTTP header)
			Form extraHeaders = new Form();
			extraHeaders.add("If-Match","*");
	//		request.getAttributes().put("org.restlet.http.headers", extraHeaders);	
			
			// probably this might work
			Conditions conditions = new Conditions();
			List<Tag> tags = new ArrayList<Tag>();
			tags.add(Tag.ALL);
			conditions.setMatch(tags);
			request.setConditions(conditions);
			
			// Setting ACCEPT headers as per:
			//   http://restlet.tigris.org/ds/viewMessage.do?dsForumId=4447&dsMessageId=18804
			request.getClientInfo().getAcceptedMediaTypes().add(
					new Preference<MediaType>(new MediaType("application/atom+xml")));
			
			request.getClientInfo().getAcceptedMediaTypes().add(
					new Preference<MediaType>(new MediaType("application/xml")));
			
			Response response = client.handle(request);		
			if(response.getStatus().equals(Status.SUCCESS_NO_CONTENT))			
				return true;
			else
				throw new APIRequestFailedException(response.getStatus());
		} catch (IOException e) {
			// CreateATOMFeed failed
			return false;
		}
	}
	
	/**
	 * Query entities in the table using the given query expression.
	 * 
	 * More information about writing query expressions can be found 
	 * <a href="http://msdn.microsoft.com/en-us/library/dd894031.aspx">
	 * here</a>.
	 * 
	 * @param queryExpression the query expression (URL-encoded) 
	 * @return A List of TableStorageEntity(s) satisfying the query.
	 * @throws APIRequestFailedException If the API request return an unexpected HTTP status code.
	 */
	public List<TableStorageEntity> queryEntities(String queryExpression)
		throws APIRequestFailedException{
		try{
			// TODO handle the continuation tokens.. Query entities will return a max of 1000 entities
			//  to retrieve further entities, u need to send another request and include these tokens
			// 	 as URL parameters as per 
			// 		http://msdn.microsoft.com/en-us/library/dd135718.aspx
			List<TableStorageEntity> entitiesList =null;		
			Client client = new Client(Protocol.HTTP); 		
			ChallengeResponse challengeResponse =  new ChallengeResponse(
		    	ChallengeScheme.HTTP_MS_SHAREDKEY_LITE,
		    	accountName,
		    	accountSharedKey);
			String url = endPoint + "/" + this.tableName + "()?$filter="+  queryExpression;
			System.out.println(url);		
			Request request = new Request(Method.GET, url);
			request.setChallengeResponse(challengeResponse);		
			Response response = client.handle(request);		
			// Now retrieve the entities if the request succeeds
			if(response.getStatus().isSuccess()){			
				InputStream responseStream = response.getEntity().getStream();
				entitiesList = ATOMHelper.getEntitiesFromATOMFeed(responseStream);
				return entitiesList;
			}else
				throw new APIRequestFailedException(response.getStatus());
		} catch (IOException e) {
			// getStream failed
			return null;
		} catch (Exception e) {
			// parsing XML failed.
			return null;
		}
	}
	
	/**
	 * Deletes the given entity(identified by PK,RK pair) from the table.
	 * 
	 * @param te The entity
	 * @return true if the entity was successfully deleted.
	 * @throws APIRequestFailedException If the API request return an unexpected HTTP status code.
	 */
	public boolean delete(TableStorageEntity te)
		throws APIRequestFailedException{		
	
		Client client = new Client(Protocol.HTTP); 
		ChallengeResponse challengeResponse =  new ChallengeResponse(
	    	ChallengeScheme.HTTP_MS_SHAREDKEY_LITE,
	    	accountName,
	    	accountSharedKey);
		
		String url = endPoint + "/" + this.tableName + "(" + 
					"PartitionKey='"+ te.getPartitionKey() +"',"+ 
					"RowKey='" +te.getRowKey() + "'" +
					")";
		
		
		Request request = new Request(Method.DELETE, url);
		request.setChallengeResponse(challengeResponse);
		
		// probably this might work
		Conditions conditions = new Conditions();
		List<Tag> tags = new ArrayList<Tag>();
		tags.add(Tag.ALL);
		conditions.setMatch(tags);
		request.setConditions(conditions);
		
		
		Response response = client.handle(request);	
		// Now retrieve the entities if the request succeeds
		if(response.getStatus().isSuccess()){			
			return true;						
		}else
			throw new APIRequestFailedException(response.getStatus());
		
	}
	
	
	public boolean clear() throws APIRequestFailedException{
		boolean result = true ;
		for(TableStorageEntity entity: this.queryEntities(""))			
			result &= this.delete(entity);	
		return result;
	}
}
